/*
**    Copyright (C) 2003-2011 Institute for Systems Biology
**                            Seattle, Washington, USA.
**
**    This library is free software; you can redistribute it and/or
**    modify it under the terms of the GNU Lesser General Public
**    License as published by the Free Software Foundation; either
**    version 3 of the License, or (at your option) any later version.
**
**    This library is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**    Lesser General Public License for more details.
**
**    You should have received a copy of the GNU Lesser General Public
**    License along with this library; if not, write to the Free Software
**    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

package org.systemsbiology.util;

import java.util.Map;
import java.util.Properties;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
 * A static library of methods to help with JSON I/O.
 * @author anorberg
 *
 */
public class JsonUtil {

	/**
	 * Not strictly a JSON method, this embeds one Properties object in another. The <code>parent</code> 
	 * object is modified to contain a new property for every field in the <code>child</code>. The
	 * properties are named as <code>prefix</code>.(child property name), functionally creating
	 * a dotted namespace.
	 * @param parent	Properties object into which the fields of <code>child</code> will be written.
	 * @param child	    Properties object to extract fields from. Will not be modified.
	 * @param prefix	Prefix to be stapled to the front of field names in <code>child</code> during
	 * 					their insertion into <code>parent</code>.
	 */
    public static void nestProperties(Properties parent, Properties child, String prefix){
    	for(Map.Entry<Object, Object> entry : child.entrySet()){
    		//Using toString even though these should already be Strings, because, hey, no casts like that.
    		parent.setProperty(
    				prefix + "." + entry.getKey().toString(),
    				entry.getValue().toString()
    			);
    	}
    }
    
    /**
     * Recursively convert a JSON array into a Properties object. Properties will
     * be numbered, as per the index, and a "length" property will also be created.
     * If a field is null, it will be omitted. If a field is complex (a JSON Object
     * or JSON Array), namespacing will take place as in propertiesFromJsonObject.
     * @param jarr JsonArray to convert.
     * @return Properties object representing a recursive view of the
     *         provided JSON array.
     */
    public static Properties propertiesFromJsonArray(JsonArray jarr){
    	Properties ret = new Properties();
    	
    	ret.setProperty("length", Integer.toString(jarr.size()));
    	
    	for(int k = 0; k < jarr.size(); ++k){
    		JsonElement e = jarr.get(k);
    		if(e.isJsonNull()){
    			continue; //null elements is null. This is why "highest value" isn't a safe "length"
    		}
    		if(e.isJsonPrimitive()){
    			if(e.getAsJsonPrimitive().isString()){
    				ret.setProperty(Integer.toString(k), e.getAsString());
    				continue;
    			}
    			ret.setProperty(Integer.toString(k), e.toString());
    			continue;
    		}
    		if(e.isJsonArray()){
    			Properties p = propertiesFromJsonArray(e.getAsJsonArray());
    			nestProperties(ret, p, Integer.toString(k));
    			continue;
    		}
    		if(e.isJsonObject()){
    			Properties p = propertiesFromJsonObject(e.getAsJsonObject());
    			nestProperties(ret, p, Integer.toString(k));
    			continue;
    		}
    		assert false: "This can't happen: A JsonElement isn't anything";
    	}
    	
    	return ret;
    }
    
    /**
     * Recursively convert a JSON object into a Properties object. If the JSON object
     * is complex (contains another JSON object or a JSON array), the resulting
     * Properties file will be namespaced: the variable inside the JSON object will
     * be the root of a set of properties beginning with that variable name, followed
     * by a dot, then followed by the interior members of the sub-object. This proceeds
     * recursively.
     * @param jobj JsonObject to convert deeply into a Properties object.
     * @return Properties object representing a recursive view of the JSON object.
     */
    public static Properties propertiesFromJsonObject(JsonObject jobj){
    	Properties ret = new Properties();
    	
    	for(Map.Entry<String, JsonElement> element : jobj.entrySet()){
    		JsonElement j = element.getValue();
    		if(j.isJsonNull()){
    			continue;
    		}
    		if(j.isJsonPrimitive()){
    			if(j.getAsJsonPrimitive().isString()){
    				ret.setProperty(element.getKey(), j.getAsString());
    				continue;
    			}
    			ret.setProperty(element.getKey(), j.toString()); //string conversion is good
    			continue;
    		}
    		if(j.isJsonArray()){
    			Properties q = propertiesFromJsonArray(j.getAsJsonArray());
    			nestProperties(ret, q, element.getKey());
    			continue;
    		}
    		if(j.isJsonObject()){
    			Properties q = propertiesFromJsonObject(j.getAsJsonObject());
    			nestProperties(ret, q, element.getKey());
    			continue;
    		}
    		assert false: "this can't happen: a JsonElement isn't anything";
    	}
    	
    	return ret;
    }
    
    /**
     * Creates a Properties object representing a shallow view of the
     * provided JSON object.
     * If any fields of the JSON object are complex (JSON objects or JSON arrays),
     * they will not be recursed into. Instead, the relevant property will contain
     * the string representation of the entire structure, which can then be re-parsed
     * via standard JSON parsing mechanisms to provide useful data.
     * @param jobj JSON object to convert into a Properties object.
     * @return Properties object representing a shallow view of the JSON object.
     */
    public static Properties shallowPropertiesFromJsonObject(JsonObject jobj){
    	Properties ret = new Properties();
    	
    	for(Map.Entry<String, JsonElement> element:jobj.entrySet()){
    		JsonElement j = element.getValue();
    		if(j.isJsonNull()){
    			continue;
    		}
    		if(j.isJsonPrimitive()){
    			if(j.getAsJsonPrimitive().isString()){
					ret.setProperty(element.getKey(), j.getAsString());
					continue;
				}
    		}
    		ret.setProperty(element.getKey(), j.toString());
    	}
    	
    	return ret;
    }
}
